/**
 * @Author: lena
 * @Date: 2021/9/24 20:57
 * @Description: 使用栈计算输入的表达式，前提自己实现一个栈
 * @Version: 1.0.0
 */

package exercise

import (
	"errors"
	"fmt"
	. "helloworld/data_structure"
	"helloworld/tools/typex"
	"strconv"
)

// 创建两个栈 一个存储操作数 一个存储操作符
var numStack = NewStack(20)
var symbolStack = NewStack(20)

// 获取输入
func StartToCalculate() {
	var input string
	fmt.Printf("请输入要计算的表达式：")
	fmt.Scanln(&input)
	res, err := calculate(input)
	if err != nil {
		fmt.Println(err)
	}
	fmt.Println("计算结果为：", res)
}

/** 计算值：（边计算边检验表达式格式是否正确）
  高于符号栈顶优先级->存
  低于符号栈顶优先级：1.符号不为)] 栈顶为[( => 入栈
                  2.符号为)]计算 直到符号栈顶为[(
                  3.数字栈出两个元素利用栈顶符号计算 存入结果和符号
*/
func calculate(str string) (int, error) {
	last := -1
	for _, s := range str {
		//fmt.Println("---------------current:",string(s))
		// 判断当前s的优先级
		p := getPriority(string(s))
		// s是数字：直接入栈
		if p == 0 {
			c, _ := typex.Int32ToInt(s) // 当前数字
			// 判断上一个是否是数字
			if last == 0 {
				n, _ := numStack.Pop()
				// 拼接数字:返回string类型
				ns := fmt.Sprintf("%v%v", n, c)
				c, _ = strconv.Atoi(ns)
			}
			// 上一个符号是'-'
			if last == 2 {
				// 将当前数字改为负数
				c = -c
				// 修改符号栈栈顶的-号 改为+号
				symbolStack.Pop()
				symbolStack.Push("+")
			}
			// 数字压入栈
			numStack.Push(c)
			last = p
		} else {
			last = p
			// s是字符
			if symbolStack.IsEmpty() {
				// 当前字符栈为空，直接入栈
				symbolStack.Push(string(s))
				continue
			}
			// 获取栈顶字符
			t, err := symbolStack.Peek()
			if !err {
				return 0, errors.New("[error]1 符号栈为空，无法出栈。")
			}
			top := getPriority(t.(string))
			// 当前符号p不为)] 但符号栈栈顶top为[( => 直接入栈
			if (p != 1 && top == 8) || (p != 2 && top == 7) {
				symbolStack.Push(string(s))
				continue
			}
			// 符号为')' = > 计算 直到符号栈顶为'('
			for p == 1 && top != 8 {
				// 符号栈出栈
				symbol, err := symbolStack.Pop()
				if !err {
					return 0, errors.New("[error]2 符号栈为空")
				}
				// 操作数出栈
				data1, err := numStack.Pop()
				data2, err := numStack.Pop()
				if !err {
					return 0, errors.New("[error]3 输入表达式有误")
				}
				res, e := getSum(data1.(int), data2.(int), symbol.(string))
				if e != nil {
					return 0, e // 不是合法的符号
				}
				// 计算结果入栈
				numStack.Push(res)
				// 更新当前栈顶元素
				t, err = symbolStack.Peek()
				if !err {
					// 栈空 直接进入下一个循环
					break
					//return 0,errors.New("[error]4 符号栈为空，无法出栈。")
				}
				top = getPriority(t.(string))
			}
			// 当前符号p位')' 符号栈顶top元素为'('
			if p == 1 && top == 8 {
				// 符号栈出栈
				symbolStack.Pop()
				// 当前符号位')'无需处理
				continue
			}
			// top=符号栈栈顶优先级 p=当前符号优先级
			// p高于符号栈栈顶优先级top：存
			if p > top {
				symbolStack.Push(string(s))
			} else {
				// p低于符号栈栈顶优先级top：计算
				// 符号栈出栈
				symbol, err := symbolStack.Pop()
				if !err {
					return 0, errors.New("[error]5 符号栈为空")
				}
				// 操作数出栈
				data1, err := numStack.Pop()
				data2, err := numStack.Pop()
				if !err {
					return 0, errors.New("[error]6 输入表达式有误")
				}
				res, e := getSum(data1.(int), data2.(int), symbol.(string))
				if e != nil {
					return 0, e // 不是合法的符号
				}
				// 计算结果入栈
				numStack.Push(res)
				// 将当前符号入栈
				symbolStack.Push(string(s))
			}
		}
	}
	// 计算栈内剩余值
	for symbolStack.Len() != 0 {
		if numStack.Len() < 2 {
			return 0, errors.New("[error]7 输入表达式有误")
		}
		// 符号栈出栈
		symbol, err := symbolStack.Pop()
		if !err {
			return 0, errors.New("[error]8 符号栈为空")
		}
		// 操作数出栈
		data1, err := numStack.Pop()
		data2, err := numStack.Pop()
		if !err {
			return 0, errors.New("[error]9 输入表达式有误")
		}
		// 计算
		res, e := getSum(data1.(int), data2.(int), symbol.(string))
		if e != nil {
			return 0, e // 不是合法的符号
		}
		// 计算结果入栈
		numStack.Push(res)
	}
	if numStack.Len() != 1 {
		return 0, errors.New("[error]10 输入表达式有误")
	}
	res, _ := numStack.Peek()
	return res.(int), nil
}

// 计算结果
func getSum(data1 int, data2 int, c string) (int, error) {
	switch c {
	// 要注意运算顺序
	case "+":
		return data1 + data2, nil
	case "-":
		return data2 - data1, nil
	case "*":
		return data1 * data2, nil
	case "/":
		return data2 / data1, nil
	}
	return 0, errors.New("[error]11 不是合法的符号")
}

// 获取符号优先级：) ] +- */ [ (
func getPriority(c string) int {
	switch c {
	case "+":
		return 3
	case "-":
		return 2
	case "*":
		return 5
	case "/":
		return 5
	case "(":
		return 8
	case ")":
		return 1
	default:
		return 0 // 数字
	}
}
